package org.ihelpuoo.orm;

import java.io.BufferedWriter;
import java.io.File;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletRequest;

import org.apache.log4j.Logger;
import org.ihelpuoo.orm.filter.Filter;
import org.ihelpuoo.orm.parser.ObjectStringParser;
import org.ihelpuoo.orm.filter.FieldFilter;
import org.ihelpuoo.orm.filter.ObjectFilter;
import org.ihelpuoo.orm.filter.SQLFilter;
import org.ihelpuoo.core.Factory;
import org.ihelpuoo.db.ConnectionWrapper;
import org.ihelpuoo.db.ResultSetProxy;
import org.ihelpuoo.io.FileUtils;
import org.ihelpuoo.io.StringBufferOutputStream;
import org.ihelpuoo.lang.NutException;
import org.ihelpuoo.lang.ObjectUtils;
import org.ihelpuoo.lang.StringUtils;
import org.ihelpuoo.util.Pager;
import org.ihelpuoo.util.Properties;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

public class III extends Factory
{
	private static Logger		log					= Logger.getLogger(III.class);
	public static final int		DEFAULT_FETCH_SIZE	= 100;
	public static final int		DEFAULT_OBJECT_ID	= 0;
	public static final byte	DUMP_MODE_FORMATTED	= 0;
	public static final byte	DUMP_MODE_COMPRESS	= 1;

	protected IIIManual			manual;

	@Override
	public void init(Properties config) throws NutException
	{
		super.init(config);
		try
		{
			manual = new IIIManual(this,config.getString("home"));
		}
		catch (Exception e)
		{
			throw new NutException(e);
		}
	}

	@Override
	public Properties getConfig()
	{
		return new IIIConfig(super.getConfig());
	}

	public IIIManual getManual()
	{
		return manual;
	}

	public Object fetchObject(ServletRequest request, Object obj, FieldFilter actived,
			FieldFilter locked) throws IIIException
	{
		return manual.getHttpProcessor().fetch(manual.getRule(obj.getClass()),request,obj,actived,
				locked, null);
	}

	public Object fetch(ServletRequest request, Object obj) throws IIIException
	{
		return manual.getHttpProcessor()
				.fetch(manual.getRule(obj.getClass()),request,obj,null,null, null);
	}

	/*
		public Object fetch(CharSequence str, Object obj, FieldFilter actived, FieldFilter locked)
				throws IIIException
		{
			if (null == str || str.length() == 0)
				return obj;
			return fetch(new CharInputStream(str),obj,actived,locked);
		}

		public Object fetch(InputStream ins, Object obj, FieldFilter actived, FieldFilter locked)
				throws IIIException
		{
			if (null == ins)
				return obj;
			return fetch(new BufferedReader(new InputStreamReader(ins)),obj,actived,locked);
		}
		public List query(CharSequence cs, Class type, FieldFilter actived, FieldFilter locked)
			throws IIIException
	{
		return this.query(new CharInputStream(cs),type,actived,locked);
	}

	@SuppressWarnings("unchecked")
	public List query(InputStream ins, Class type, FieldFilter actived, FieldFilter locked)
			throws IIIException
	{
		List list = new LinkedList();
		try
		{
			int c;
			while (-1 != (c = ins.read()))
			{
				if ('{' != c)
					throw new IIIException("Each bean content must be nested by '{' and '}'");
				Object b = (Object)type.newInstance();
				fetch(ins,b,actived,locked);
				list.add(b);
			}
		}
		catch (Exception e)
		{
			throw IIIException.wrap(e);
		}
		return list;
	}

	public Map<String, Object> buildMap(InputStream ins, Class type, FieldFilter actived,
			FieldFilter locked) throws IIIException
	{
		ObjectRule r = manual.getRule(type);
		List list = query(ins,type,actived,locked);
		return buildMapByList(list,r);
	}

	public Map<String, Object> buildMap(CharSequence cs, Class type, FieldFilter actived,
			FieldFilter locked) throws IIIException
	{
		ObjectRule r = manual.getRule(type);
		List list = query(cs,type,actived,locked);
		return buildMapByList(list,r);
	}

	public Object fetch(Reader rd, Object obj, FieldFilter actived, FieldFilter locked)
			throws IIIException
	{
		if (null == rd)
			return obj;
		ObjectStringParser bsp = new ObjectStringParser();
		try
		{
			bsp.parse(rd);
		}
		catch (LoaderException e)
		{
			throw IIIException.wrap(e);
		}
		this.fetch(bsp.getMap(),obj,actived,locked);
		return obj;
	}
	*/

	public Object fetch(Map map, Object obj, FieldFilter actived, FieldFilter locked)
			throws IIIException
	{
		return manual.getMapProcessor()
				.fetch(manual.getRule(obj.getClass()),map,obj,actived,locked, null);
	}

	public Object fetch(Connection conn, Object obj) throws IIIException
	{
		return this.fetch(conn,obj,null,null);
	}

	public Object fetch(Connection conn, Object obj, FieldFilter actived, FieldFilter locked)
			throws IIIException
	{
		return manual.getConnectionProcessor().fetch(manual.getRule(obj.getClass()),conn,obj,
				actived,locked, null);
	}

	public Object fetch(Connection conn, ResultSet rs, Object obj, FieldFilter actived,
			FieldFilter locked) throws IIIException
	{
		return manual.getResultSetProcessor().fetch(manual.getRule(obj.getClass()),
				ResultSetProxy.wrap(rs,conn),obj,actived,locked, null);
	}

	public Object fetchByXMLFileName(String fn, Object obj, FieldFilter actived, FieldFilter locked)
			throws IIIException
	{
		return fetchByXMLFile(FileUtils.findFile(fn),obj,actived,locked);
	}

	public Object fetchByXMLFile(File f, Object obj, FieldFilter actived, FieldFilter locked)
			throws IIIException
	{
		if (null == f || !f.exists())
			return null;
		try
		{
			Document doc = manual.dom().parse(f);
			return this.fetch(doc,obj,actived,locked);
		}
		catch (Exception e)
		{
			throw IIIException.wrap(e);
		}
	}

	public Object fetch(Document doc, Object obj) throws IIIException
	{
		return this.fetch(doc,obj,null,null);
	}

	public Object fetch(Document doc, Object obj, FieldFilter actived, FieldFilter locked)
			throws IIIException
	{
		return manual.getDomProcessor().fetch(manual.getRule(obj.getClass()),
				doc.getDocumentElement(),obj,actived,locked, null);
	}

	public Object fetch(Element ele, Object obj) throws IIIException
	{
		return this.fetch(ele,obj,null,null);
	}

	public Object fetch(Element ele, Object obj, FieldFilter actived, FieldFilter locked)
			throws IIIException
	{
		return manual.getDomProcessor()
				.fetch(manual.getRule(obj.getClass()),ele,obj,actived,locked, null);
	}

	public List query(Connection conn, Class type, Filter filter, FieldFilter actived,
			FieldFilter locked, Pager pager, Object refer) throws IIIException
	{
		return manual.getConnectionProcessor().query(manual.getRule(type),conn,type,filter,actived,
				locked,pager,refer);
	}

	public List query(Connection conn, Object obj, FieldFilter actived, FieldFilter locked,
			Pager pager, Object refer) throws IIIException
	{
		ObjectRule rule = manual.getRule(obj.getClass());
		rule.checkDatabaseConfig(conn,refer);
		Filter filter = new ObjectFilter(rule,obj,actived,locked);
		return manual.getConnectionProcessor().query(rule,conn,obj.getClass(),filter,actived,
				locked,pager,refer);
	}

	public List query(Connection conn, Object obj) throws IIIException
	{
		return this.query(conn,obj,null,null,null,null);
	}

	public List query(Connection conn, Class type, Filter filter, FieldFilter actived,
			FieldFilter locked, Pager pager) throws IIIException
	{
		return this.query(conn,type,filter,actived,locked,pager,null);
	}

	public List query(Connection conn, Class type, Filter filter, Pager pager, Object refer)
			throws IIIException
	{
		return this.query(conn,type,filter,null,null,pager,refer);
	}

	public List query(Connection conn, Class type) throws IIIException
	{
		return this.query(conn,type,null,null,null,null,null);
	}

	public List query(File dir, Class type, Filter filter, FieldFilter actived, FieldFilter locked,
			Pager pager, Object refer) throws IIIException
	{
		return manual.getXmlFileProcessor().query(manual.getRule(type),dir,type,filter,actived,
				locked,pager,refer);
	}

	public List query(Element ele, Class type, Filter filter, FieldFilter actived,
			FieldFilter locked, Pager pager, Object refer) throws IIIException
	{
		return manual.getDomProcessor().query(manual.getRule(type),ele,type,filter,actived,locked,
				pager,refer);
	}

	public Map<String, Object> buildMap(Connection conn, Class type, Filter filter,
			FieldFilter actived, FieldFilter locked, Pager pager, Object refer) throws IIIException
	{
		ObjectRule r = manual.getRule(type);
		List list = manual.getConnectionProcessor().query(r,conn,type,filter,actived,locked,pager,
				refer);
		return buildMapByList(list,r);
	}

	public Map<String, Object> buildMap(File dir, Class type, Filter filter, FieldFilter actived,
			FieldFilter locked, Pager pager, Object refer) throws IIIException
	{
		ObjectRule r = manual.getRule(type);
		List list = manual.getXmlFileProcessor().query(r,dir,type,filter,actived,locked,pager,null);
		return buildMapByList(list,r);
	}

	public Map<String, Object> buildMap(Element ele, Class type, Filter filter,
			FieldFilter actived, FieldFilter locked, Pager pager, Object refer) throws IIIException
	{
		ObjectRule r = manual.getRule(type);
		List list = manual.getDomProcessor().query(r,ele,type,filter,actived,locked,pager,refer);
		return buildMapByList(list,r);
	}

	public static Map<String, Object> buildMapByList(IIIManual manual, List list)
			throws IIIException
	{
		if (null == list || list.size() == 0)
			return new HashMap<String, Object>();
		Object b = list.get(0);
		ObjectRule r = manual.getRule(b.getClass());
		return buildMapByList(list,r);
	}

	public static Map<String, Object> buildMapByList(List list, ObjectRule r) throws IIIException
	{
		Map<String, Object> map = new HashMap<String, Object>();
		if (null == list || list.size() == 0)
			return map;

		Field key = r.getKeyField(list);
		Class type = r.getType();
		if (null == key)
			throw new IIIException("Fail to find key of BeanRule \"" + type.getName() + "\"!");

		try
		{
			Method getter = ObjectUtils.getGetter(type,key);
			Iterator it = list.iterator();
			while (it.hasNext())
			{
				Object o = it.next();
				Object v = getter.invoke(o,(Object[])null);
				if (null == v)
					continue;
				String k = v.toString();
				map.put(k,o);
			}
		}
		catch (NoSuchMethodException e)
		{
			throw new IIIException("Fail to find getter for field '" + key.getName() + "' of '"
					+ type.getName() + "'!");
		}
		catch (Exception e)
		{
			throw new IIIException("Fail to invoke getter for field '" + key.getName() + "' of '"
					+ type.getName() + "'!");
		}

		return map;
	}

	public boolean delete(Connection conn, Object obj, FieldFilter ignore) throws IIIException
	{
		return manual.getConnectionProcessor().delete(manual.getRule(obj.getClass()),conn,obj,
				ignore);
	}

	public List deleteBatch(Connection conn, Class type, SQLFilter filter, FieldFilter ignore,
			Object refer) throws IIIException
	{
		List list = this.query(conn,type,filter,null,ignore,null);
		if (null != list)
		{
			Iterator it = list.iterator();
			while (it.hasNext())
				this.delete(conn,it.next(),ignore);
		}
		return list;
	}

	public boolean clear(Connection conn, Class type) throws IIIException
	{
		return this.clear(conn,type,null);
	}

	public boolean clear(Connection conn, Class type, Object refer) throws IIIException
	{
		try
		{
			ObjectRule br = manual.getRule(type);
			String tableName = br.getTableName(refer);
			String sql = "TRUNCATE " + tableName + ";";
			if (log.isDebugEnabled())
				log.debug(sql);
			ConnectionWrapper.wrap(conn).execute(sql);
		}
		catch (SQLException e)
		{
			throw IIIException.wrap(e);
		}
		return true;
	}

	public boolean clearDeeply(Connection conn, Class type, Object refer) throws IIIException
	{
		ObjectRule r = manual.getRule(type);
		FieldRule[] frs = r.getComponentFields();
		for (int i = 0; i < frs.length; i++)
		{
			clear(conn,frs[i].getComponentType(),refer);
		}
		return clear(conn,type,refer);
	}

	public boolean clearDeeply(Connection conn, Class type) throws IIIException
	{
		return clearDeeply(conn,type,null);
	}

	public int count(Connection conn, Class type, SQLFilter filter, Object refer)
			throws IIIException
	{
		int re = 0;
		try
		{
			ObjectRule br = manual.getRule(type);
			String tableName = br.getTableName(refer);
			StringBuffer sql = new StringBuffer("SELECT count(*) FROM " + tableName);
			String condition = null;
			if (null != filter)
				condition = filter.getString();
			if (!StringUtils.isBlank(condition))
				sql.append(" WHERE ").append(condition);

			if (log.isDebugEnabled())
				log.debug(sql);

			Statement stat = conn.createStatement();
			ResultSet rs = stat.executeQuery(sql.toString());
			if (rs.first())
				re = rs.getInt(1);

			rs.close();
			stat.close();
		}
		catch (SQLException e)
		{
			throw IIIException.wrap(e);
		}
		return re;
	}

	public int count(Connection conn, Class type) throws IIIException
	{
		return this.count(conn,type,null,null);
	}

	public int count(Connection conn, Class type, Object refer) throws IIIException
	{
		return this.count(conn,type,null,refer);
	}

	public Object save(Connection conn, Object obj) throws IIIException
	{
		return save(conn,obj,null,null);
	}

	public Object save(Connection conn, Object obj, FieldFilter actived, FieldFilter locked)
			throws IIIException
	{
		ObjectRule r = manual.getRule(obj.getClass());
		r.checkDatabaseConfig(conn,obj);
		if (r.isNewInDB(obj))
		{
			return manual.getConnectionProcessor().insert(r,conn,obj,actived,locked);
		}
		return manual.getConnectionProcessor().update(r,conn,obj,actived,locked);
	}

	public List save(Connection conn, List list) throws IIIException
	{
		return save(conn,list,null,null);
	}

	@SuppressWarnings("unchecked")
	public List save(Connection conn, List list, FieldFilter actived, FieldFilter locked)
			throws IIIException
	{
		if (null == list)
			return null;
		List nl = new ArrayList(list.size());
		Iterator it = list.iterator();
		while (it.hasNext())
		{
			Object obj = it.next();
			nl.add(this.save(conn,obj,actived,locked));
		}
		return nl;
	}

	@SuppressWarnings("unchecked")
	public List save(Connection conn, Object... list) throws IIIException
	{
		if (null == list)
			return null;
		List nl = new ArrayList(list.length);
		for (int i = 0; i < list.length; i++)
		{
			nl.add(save(conn,list[i]));
		}
		return nl;
	}

	private Object insert(Document doc, Object obj, FieldFilter actived, FieldFilter locked)
			throws IIIException
	{
		return manual.getDomProcessor().insert(manual.getRule(obj.getClass()),doc,obj,actived,
				locked);
	}

	public Object insert(Element ele, Object obj) throws IIIException
	{
		return insert(ele,obj,null,null);
	}

	public Object insert(Element ele, Object obj, FieldFilter actived, FieldFilter locked)
			throws IIIException
	{
		return manual.getDomProcessor().insert(manual.getRule(obj.getClass()),ele,obj,actived,
				locked);
	}

	public List insert(Element ele, List list, FieldFilter actived, FieldFilter locked)
			throws IIIException
	{
		if (null == list || list.size() == 0)
			return list;
		Iterator it = list.iterator();
		while (it.hasNext())
		{
			Object obj = it.next();
			insert(ele,obj,actived,locked);
		}
		return list;
	}

	public List insert(Element ele, List list) throws IIIException
	{
		return insert(ele,list,null,null);
	}

	public void insert(Writer w, Object obj, FieldFilter actived, FieldFilter locked)
			throws IIIException
	{
		insert(w,obj,III.DUMP_MODE_COMPRESS,actived,locked);
	}

	public void insert(Writer w, Object obj, byte mode) throws IIIException
	{
		insert(w,obj,mode,null,null);
	}

	public void insert(Writer w, Object obj, byte mode, FieldFilter actived, FieldFilter locked)
			throws IIIException
	{
		try
		{
			if (null == obj)
			{
				w.write("NULL-BEAN");
				return;
			}
			Map<String, Object> map = new HashMap<String, Object>();
			manual.getMapProcessor().update(manual.getRule(obj.getClass()),map,obj,actived,locked);
			BeanStringParserSetter bsps = new BeanStringParserSetter();
			bsps.setMap(map);
			bsps.setDumpMode(mode);

			bsps.render2(w);
		}
		catch (Exception e)
		{
			throw IIIException.wrap(e);
		}
	}

	public String dump(Object obj)
	{
		return dump(obj,III.DUMP_MODE_COMPRESS);
	}

	public String dump(Object obj, byte mode)
	{
		StringBuffer sb = new StringBuffer();
		Writer w = new BufferedWriter(new OutputStreamWriter(new StringBufferOutputStream(sb)));
		try
		{
			insert(w,obj,mode);
			w.close();
		}
		catch (Throwable e)
		{
			sb.append(e.getMessage());
		}
		return sb.toString();
	}

	public Document createDocument(Object obj) throws IIIException
	{
		Document doc = manual.dom().newDocument();
		insert(doc,obj,null,null);
		return doc;
	}

	public Document createDocument(Object obj, FieldFilter actived, FieldFilter locked)
			throws IIIException
	{
		Document doc = manual.dom().newDocument();
		insert(doc,obj,actived,locked);
		return doc;
	}

	public Document createDocument(List list) throws IIIException
	{
		return createDocument(list,null,null);
	}

	public Document createDocument(List list, FieldFilter actived, FieldFilter locked)
			throws IIIException
	{
		Document doc = manual.dom().newDocument();
		Element root = doc.createElement("root");
		doc.appendChild(root);
		if (null != list)
		{
			Iterator it = list.iterator();
			while (it.hasNext())
				insert(root,it.next(),actived,locked);
		}
		return doc;
	}

	public Document createDocument(Object[] list, int off) throws IIIException
	{
		int len = (null == list ? 0 : list.length - off);
		return createDocument(list,off,len);
	}

	public Document createDocument(Object[] list, int off, int len) throws IIIException
	{
		Document doc = manual.dom().newDocument();
		Element root = doc.createElement("root");
		doc.appendChild(root);

		if (null != list)
		{
			for (int i = 0; i < len; i++)
				insert(root,list[i + off],null,null);
		}
		return doc;
	}

	public Document createDocument(Object... list) throws IIIException
	{
		return createDocument(list,0);
	}

	/**
	 * <b>BeanStringParserSetter class:</b>
	 * <hr color=grey size=1>
	 *
	 * Make the BeanStringParser class setMap visiable for BeanFactory.
	 *
	 * @author zozoh  created @ 2007
	 *
	 */
	private class BeanStringParserSetter extends ObjectStringParser
	{

		@Override
		protected void setMap(Map<String, Object> map)
		{
			super.setMap(map);
		}

	}

	@Override
	public void depose() throws NutException
	{
	}
}
